package log

import (
	"fmt"
	"io"
	"log"
	"os"
	"path"
	"runtime"
	"strings"
	"time"
)

func init() {
	Config = &config{
		LogLevel:        LOG_LEVEL_INFO,
		MaxLogFileSize:  1024 * 1024 * 10, // 10MB
		MaxLogFileCount: 10,
		Logger:          log.New(os.Stdout, "", 0),
		logBuffer:       make(chan string, 1024*1024),
	}

	go func() {
		for {
			line := <-Config.logBuffer
			if Config.Logger != nil {
				Config.Logger.Print(line)
			}
		}
	}()
}

const (
	LOG_LEVEL_DEBUG = 1
	LOG_LEVEL_INFO  = 2
	LOG_LEVEL_WARN  = 3
	LOG_LEVEL_ERROR = 4
)

type Reporter interface {
	Report(content string) error
}

var Config *config

type config struct {
	LogLevel        int
	MaxLogFileSize  int64 // 单位: bit
	MaxLogFileCount int64
	Logger          *log.Logger
	Async           bool
	LogReporter     Reporter
	LogWriter       io.WriteCloser
	logBuffer       chan string
}

// InitLogger init logger with init configurations
func InitLogger(filename string) {
	var err error
	Config.LogWriter, err = NewLogFile(filename, Config.MaxLogFileSize, Config.MaxLogFileCount)
	if err != nil {
		log.Fatal(err)
	}
	Config.Logger = log.New(Config.LogWriter, "", 0)
}

func Debugf(format string, v ...interface{}) {
	globalLogger.Debugf(format, v...)
}

func SlowInfof(format string, v ...interface{}) {
	globalLogger.Infof(format, v...)
}

func Infof(format string, v ...interface{}) {
	globalLogger.Infof(format, v...)
}

func Warnf(format string, v ...interface{}) {
	globalLogger.Warnf(format, v...)
}

func Errorf(format string, v ...interface{}) {
	globalLogger.Errorf(format, v...)
}

var globalLogger = &Logger{callerStartLevel: LOG_LEVEL_INFO}

type Logger struct {
	callerStartLevel int
}

func GetLogger(callerStartLevel int) *Logger {
	return &Logger{
		callerStartLevel: callerStartLevel,
	}
}

func (lg Logger) Debugf(format string, v ...interface{}) {
	lg.log(LOG_LEVEL_DEBUG, "DEBUG", format, v...)
}

func (lg Logger) Infof(format string, v ...interface{}) {
	lg.log(LOG_LEVEL_INFO, "INFO", format, v...)
}

// SlowInfof @Deprecated
func (lg Logger) SlowInfof(format string, v ...interface{}) {
	lg.log(LOG_LEVEL_INFO, "INFO", format, v...)
}

func (lg Logger) Warnf(format string, v ...interface{}) {
	lg.log(LOG_LEVEL_WARN, "WARN", format, v...)
}

func (lg Logger) Errorf(format string, v ...interface{}) {
	lg.log(LOG_LEVEL_ERROR, "ERROR", format, v...)
}

// 输出格式: 2022-02-25 07:49:19.210|Error|logger_test.go:19: abc=1231, 123123=4124
func (lg Logger) log(logLevel int, logLevelStr string, contentFormat string, vals ...interface{}) {
	if Config.Logger == nil || Config.LogLevel > logLevel {
		return
	}

	logLine := ""
	file, line, ok := getActualCallerWithStartLevel(lg.callerStartLevel + 2)
	if ok {
		args := []interface{}{time.Now().Format("2006-01-02 15:04:05.000"), logLevelStr, path.Base(file), line}
		args = append(args, vals...)
		logLine = fmt.Sprintf("%s|%s|%s:%d|"+contentFormat, args...)
	} else {
		logLine = fmt.Sprintf("[%s]: "+contentFormat, logLevelStr, vals)
	}
	if Config.Async {
		Config.logBuffer <- logLine
	} else {
		Config.Logger.Print(logLine)
	}
}

func getActualCallerWithStartLevel(startLevel int) (file string, line int, ok bool) {
	// Get func name of caller in this file
	cpc, _, _, ok := runtime.Caller(1)
	if !ok {
		return "", 0, false
	}
	callerFuncPtr := runtime.FuncForPC(cpc)
	if callerFuncPtr == nil {
		return "", 0, false
	}
	// 调用栈中打印日志的函数
	var pc uintptr
	for callLevel := startLevel; callLevel <= 4; callLevel++ {
		pc, file, line, ok = runtime.Caller(callLevel)
		if !ok {
			return "", 0, false
		}
		funcPtr := runtime.FuncForPC(pc)
		if funcPtr == nil {
			ok = false
			return "", 0, false
		}
		if getFuncNameWithoutPackage(funcPtr.Name()) != getFuncNameWithoutPackage(callerFuncPtr.Name()) {
			return file, line, true
		}
	}
	return "", 0, false
}

func getFuncNameWithoutPackage(name string) string {
	pos := strings.LastIndex(name, ".")
	if pos >= 0 {
		return name[pos+1:]
	}
	return name
}
